#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

import os
import sys
import subprocess
import glob
from random import choice
import string
from optparse import OptionParser
import subprocess
import shutil
import socket

# squelch mysqldb spurious warnings
import warnings
warnings.simplefilter('ignore')
# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ----
# ---- We do this so cloud_utils can be looked up in the following order:
# ---- 1) Sources directory
# ---- 2) waf configured PYTHONDIR
# ---- 3) System Python path
for pythonpath in (
        "@PYTHONDIR@",
        os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"),
    ):
        if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath)
# ---- End snippet of code ----
from cloud_utils import check_selinux, CheckFailed, resolves_to_ipv6

def runCmd(cmds):
    process = subprocess.Popen(' '.join(cmds), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = process.communicate()
    if process.returncode != 0:
        raise Exception(stderr)
    return stdout.decode('utf-8')

class DBDeployer(object):
    parser = None
    options = None
    args = None
    serversetup = None
    ip = None
    user,password,host,port,rootuser,rootpassword = [None,None,None,None,None,None]
    isDebug = False
    mgmtsecretkey = None
    dbsecretkey = None
    encryptiontype = None
    dbConfPath = r"@MSCONF@"
    dbFilesPath = r"@SETUPDATADIR@"
    dbDotProperties = {}
    dbDotPropertiesIndex = 0
    encryptionKeyFile = '@MSCONF@/key'
    encryptionJarPath = '@COMMONLIBDIR@/lib/cloudstack-utils.jar'
    success = False
    magicString = 'This_is_a_magic_string_i_think_no_one_will_duplicate'
    tmpMysqlFile = os.path.join(os.path.expanduser('~/'), 'cloudstackmysql.tmp.sql')
    mysqlBinPath = None
    skipUsersAutoCreation = False

    def preRun(self):
        def backUpDbDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            copyPath = os.path.join(self.dbConfPath, 'db.properties.origin')

            if os.path.isfile(dbpPath):
                shutil.copy2(dbpPath, copyPath)

        backUpDbDotProperties()

    def postRun(self):
        def cleanOrRecoverDbDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            copyPath = os.path.join(self.dbConfPath, 'db.properties.origin')
            if os.path.isfile(copyPath):
                if not self.success:
                    shutil.copy2(copyPath, dbpPath)
                os.remove(copyPath)

        cleanOrRecoverDbDotProperties()
        if os.path.exists(self.tmpMysqlFile):
            os.remove(self.tmpMysqlFile)


    def info(self, msg, result=None):
        output = ""
        if msg is not None:
            output = "%-80s"%msg

        if result is True:
            output += "[ \033[92m%-2s\033[0m ]\n"%"OK"
        elif result is False:
            output += "[ \033[91m%-6s\033[0m ]\n"%"FAILED"
        sys.stdout.write(output)
        sys.stdout.flush()

    def debug(self, msg):
        msg = "DEBUG:%s"%msg
        sys.stdout.write(msg)
        sys.stdout.flush()

    def putDbProperty(self, key, value):
        if key in self.dbDotProperties:
            (oldValue, index) = self.dbDotProperties[key]
            self.dbDotProperties[key] = (value, index)
        else:
            self.dbDotProperties[key] = (value, self.dbDotPropertiesIndex)
            self.dbDotPropertiesIndex += 1

    def getDbProperty(self, key):
        if key not in self.dbDotProperties:
            return None
        (value, index) = self.dbDotProperties[key]
        return value

    def areCloudDatabasesCreated(self):
        cmd = "SELECT CASE WHEN COUNT(DISTINCT SCHEMA_NAME) >= 1 THEN 1 ELSE 0 END AS schema_exists \
              FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME IN ('cloud', 'cloud_usage');"
        databases = self.runMysql(cmd, "databases", self.rootuser != None)
        return databases.replace("schema_exists", "").strip() == "1"

    def runMysql(self, text, table, isRoot=False):
        kwargs = {}
        if not isRoot:
            kwargs['user'] = self.user
            if self.password != '': kwargs['passwd'] = self.password
        else:
            kwargs['user'] = self.rootuser
            if self.rootpassword != '': kwargs['passwd'] = self.rootpassword

        kwargs['port'] = self.port
        kwargs['host'] = self.host
        if self.mysqlBinPath is not None:
          mysqlPath = '"' + self.mysqlBinPath + os.sep + "mysql" + '"'
        else:
          mysqlPath = "mysql"
        try:
            mysqlCmds = [mysqlPath, '--user=%s'%kwargs['user'], '--host=%s'%kwargs['host'], '--port=%s'%kwargs['port']]
            if 'passwd' in kwargs:
                mysqlCmds.append('--password=\'%s\''%kwargs['passwd'])
            open(self.tmpMysqlFile, 'w').write(text)
            mysqlCmds.append('<')
            mysqlCmds.append(self.tmpMysqlFile)
            return runCmd(mysqlCmds)

        except Exception as e:
            err = '''Encountering an error when executing mysql script
----------------------------------------------------------------------
table:
%s

Error:
%s

Sql parameters:
%s
----------------------------------------------------------------------
            '''%(table, e.__str__(), kwargs)
            self.errorAndExit(err)

    def errorAndContinue(self, msg):
        sys.stderr.write(msg)
        sys.stderr.flush()

    def errorAndExit(self, msg):
        self.postRun()
        err = '''\n\nWe apologize for below error:
***************************************************************
%s
***************************************************************
Please run:

    cloudstack-setup-databases -h

for full help
''' % msg
        sys.stderr.write(err)
        sys.stderr.flush()
        sys.exit(1)

    def setupDBSchema(self):
        if not self.options.schemaonly and not self.rootuser:
            self.info("No mysql root user specified, will not create Cloud DB schema\n", None)
            return

        if self.areCloudDatabasesCreated() and not self.options.schemaonly and not self.options.forcerecreate:
            self.errorAndExit("Aborting script as the databases (cloud, cloud_usage) already exist.\n" \
                              "Please use the --force-recreate parameter if you want to recreate the databases and schemas, or use --schema-only if you only want to create the schemas only.")

        replacements = (
                ("CREATE USER cloud identified by 'cloud';",
                    "CREATE USER %s@`localhost` identified by '%s'; CREATE USER %s@`%%` identified by '%s';"%(
                            (self.user,self.password,self.user,self.password)
                        )),
                ("cloud identified by 'cloud';",
                    "%s identified by '%s';"%(self.user,self.password)),
                ("cloud@`localhost` identified by 'cloud'",
                    "%s@`localhost` identified by '%s'"%(self.user,self.password)),
                ("cloud@`%` identified by 'cloud'",
                    "%s@`%%` identified by '%s'"%(self.user,self.password)),
                ("to cloud@`localhost`",
                    "to %s@`localhost`"%self.user),
                ("to cloud@`%`",
                    "to %s@`%%`"%self.user),
                ("TO cloud@`localhost`",
                    "to %s@`localhost`"%self.user),
                ("TO cloud@`%`",
                    "to %s@`%%`"%self.user),
                ("WHERE `User` = 'cloud' and host =",
                    "WHERE `User` = '%s' and host ="%self.user),
                ("DROP USER 'cloud'",
                    "DROP USER '%s'"%self.user),
                ("CALL `test`.`drop_user_if_exists`() ;",
                    ""),
            )

        queriesToSkip = (
            ("CREATE USER cloud@`localhost` identified by 'cloud';", ""),
            ("CREATE USER cloud@`%` identified by 'cloud';", ""),
            ("GRANT ALL ON cloud.* to cloud@`localhost`;", ""),
            ("GRANT ALL ON cloud.* to cloud@`%`;", ""),
            ("GRANT ALL ON cloud_usage.* to cloud@`localhost`;", ""),
            ("GRANT ALL ON cloud_usage.* to cloud@`%`;", ""),
            ("GRANT process ON *.* TO cloud@`localhost`;", ""),
            ("GRANT process ON *.* TO cloud@`%`;", ""),
            ("DROP USER 'cloud'@'localhost' ;", "DO NULL;"),
            ("DROP USER 'cloud'@'%' ;", "DO NULL;")
        )

        scriptsToRun = ["create-database","create-schema", "create-database-premium","create-schema-premium"]
        if self.options.schemaonly:
            scriptsToRun = ["create-schema", "create-schema-premium"]

        for f in scriptsToRun:
            p = os.path.join(self.dbFilesPath,"%s.sql"%f)
            if not os.path.exists(p): continue
            text = open(p).read()
            if self.options.skipUsersAutoCreation:
                for t, r in queriesToSkip: text = text.replace(t,r)
            for t, r in replacements: text = text.replace(t,r)
            self.info("Applying %s"%p)
            self.runMysql(text, p, self.rootuser != None)
            self.info(None, True)

        if self.serversetup:
            conf = os.path.join(self.dbConfPath, 'db.properties')
            pcp = os.path.pathsep.join( glob.glob( os.path.join ( r"@PREMIUMJAVADIR@" , "*" ) ) )
            systemjars = r"@SYSTEMJARS@".split()
            try:
                output = runCmd(['build-classpath'] + systemjars)
                systemcp = output.strip()
            except Exception as e:
                systemcp = r"@SYSTEMCLASSPATH@"
            mscp = r"@MSCLASSPATH@"
            depscp = r"@DEPSCLASSPATH@"
            classpath = os.path.pathsep.join([pcp,systemcp,depscp,mscp,conf])
            try:
                runCmd(["java","-cp",classpath,"com.cloud.test.DatabaseConfig", self.serversetup])
            except Exception as e:
                self.errorAndExit("Apply %s failed"%self.serversetup)
        else:
            p = os.path.join(self.dbFilesPath, 'server-setup.sql')
            text = open(p).read()
            self.info("Applying %s"%p)
            self.runMysql(text, p, self.rootuser != None)
            self.info(None, True)

        for f in ["templates"]:
            p = os.path.join(self.dbFilesPath,"%s.sql"%f)
            text = open(p).read()
            self.info("Applying %s"%p)
            self.runMysql(text, p, self.rootuser != None)
            self.info(None, True)

        p = os.path.join(self.dbFilesPath,"schema-level.sql")
        if os.path.isfile(p):
            text = open(p).read()
            self.info("Applying %s"%p)
            self.runMysql(text, p, self.rootuser != None)
            self.info(None, True)

    def prepareDBFiles(self):
        def prepareDBDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            dbproperties = open(dbpPath).read().splitlines()
            newdbp = []
            emptyLine = 0
            for line in dbproperties:
                passed = False
                line = line.strip()
                if line.startswith("#"): key = line; value = ''; passed = True
                if line == '' or line == '\n': key = self.magicString + str(emptyLine); value = ''; emptyLine += 1; passed = True

                try:
                    if not passed:
                        (key, value) = line.split('=', 1)
                        if key == "cluster.node.IP": value = self.ip
                        if key == "db.cloud.username": value = self.user
                        if key == "db.cloud.password": value = self.password
                        if key == "db.cloud.host": value = self.host
                        if key == "db.cloud.port": value = self.port
                        if key == "db.usage.username": value = self.user
                        if key == "db.usage.password": value = self.password
                        if key == "db.usage.host": value = self.host
                        if key == "db.usage.port": value = self.port
                except Exception as e:
                    err = '''Wrong format in %s (%s):
Besides comments beginning "#" and empty line, all key-value pairs must be in formula of
                    key=value
for example:
                    db.cloud.username = cloud
                    ''' % (dbpPath, line)
                    self.errorAndExit(err)
                self.putDbProperty(key, value)
            self.info("Preparing %s"%dbpPath, True)
            self.putDbProperty("region.id", self.options.regionid)

        prepareDBDotProperties()

    def finalize(self):
        def finalizeDbProperties():
            entries = []
            for key in list(self.dbDotProperties.keys()):
                (value, index) = self.dbDotProperties[key]
                if key.startswith("#"):
                    entries.insert(index, key)
                elif key.startswith(self.magicString):
                    entries.insert(index, '')
                else:
                    entries.insert(index, "%s=%s"%(key, value))
            open(os.path.join(self.dbConfPath, 'db.properties'), 'w').write('\n'.join(entries))

        self.info("Finalizing setup ...", None)
        finalizeDbProperties()
        self.info(None, True)
        self.success = True # At here, we have done successfully and nothing more after this flag is set

    def grabSystemInfo(self):
        def getIpAddr():
            try:
                ip = socket.gethostbyname(socket.gethostname())
                return ip
            except Exception as e:
                return "127.0.0.1"

        if not self.ip:
            self.ip = getIpAddr()
            self.info("Detected local IP address as %s, will use as cluster management server node IP" % self.ip, True)

    def checkSystemSetup(self):
        def checkCloudDbFiles():
            self.info("Checking Cloud database files ...", None)
            dbfpaths = [ os.path.join(self.dbConfPath,"db.properties") ]
            for dbf in dbfpaths:
                if not os.path.exists(dbf):
                    self.errorAndExit("Cannot find %s"%dbf)

            coreSchemas = ['create-database.sql', 'create-schema.sql', 'templates.sql']
            if not self.serversetup:
                coreSchemas.append('server-setup.sql')

            checkingList = [os.path.join(self.dbFilesPath, x) for x in coreSchemas]
            checkingList.append(self.encryptionJarPath)
            for f in checkingList:
                if not os.path.isfile(f):
                    self.errorAndExit("Cloud DB required file %s was not found"%f)
            self.info(None, True)

        def checkDbserverHostname():
            self.info("Checking mysql server hostname ...", None)
            if resolves_to_ipv6(self, self.port):
                err = "%s resolves to an IPv6 address.  The CloudStack does not support IPv6 yet.\nPlease fix this issue in either /etc/hosts or your DNS configuration.\n"%self.host
                self.errorAndExit(err)
            self.info(None, True)

        def checkHostName():
            self.info("Checking local machine hostname ...", None)
            try:
                output= socket.getfqdn()
            except Exception as e:
                err = "The host name of this computer does not resolve to an IP address.\nPlease use your operating system's network setup tools to fix this ('hostname --fqdn' %s).\n"%e.__str__()
                self.errorAndExit(err)
            self.info(None, True)

        def checkSELinux():
            self.info("Checking SELinux setup ...", None)
            try:
                check_selinux()
            except CheckFailed as e:
                self.info("checkSelinux failed with error continuing...", None)
                self.errorAndContinue(e.__str__())
            except OSError as e:
                if e.errno == 2: pass
                else:
                    self.info("checkSelinux failed with error continuing...", None)
                    self.errorAndContinue(e.__str__())
            self.info(None, True)

        checkCloudDbFiles()
        checkHostName()
        checkSELinux()

    def processEncryptionStuff(self):
        def encrypt(value):
            cmd = ['java','-classpath','"' + self.encryptionJarPath + '"','com.cloud.utils.crypt.EncryptionCLI','-i','"' + value + '"', '-p', '"' +
                   self.mgmtsecretkey + '"', self.encryptorVersion]
            return str(runCmd(cmd)).strip('\r\n')

        def saveMgmtServerSecretKey():
            if self.encryptiontype == 'file':
                try:
                    open(self.encryptionKeyFile, 'w').write(self.mgmtsecretkey)
                except IOError as e:
                    msg = "Failed to save management server secret key file %s due to %s, also please check the default umask"%(self.encryptionKeyFile, e.strerror)
                    self.errorAndExit(msg)
                os.chmod(self.encryptionKeyFile, 0o640)
                shutil.chown(self.encryptionKeyFile, user=None, group="cloud")

        def formatEncryptResult(value):
            return 'ENC(%s)'%value

        def encryptDBSecretKey():
            self.putDbProperty('db.cloud.encrypt.secret', formatEncryptResult(encrypt(self.dbsecretkey)))
            self.putDbProperty("db.cloud.encryptor.version", self.options.encryptorVersion)

        def encryptDBPassword():
            dbPassword = self.getDbProperty('db.cloud.password')
            if dbPassword == '': return # Don't encrypt empty password
            if dbPassword == None: self.errorAndExit('Cannot find db.cloud.password in %s'%os.path.join(self.dbConfPath, 'db.properties'))
            self.putDbProperty('db.cloud.password', formatEncryptResult(encrypt(dbPassword)))

            usagePassword = self.getDbProperty('db.usage.password')
            if usagePassword == '': return # Don't encrypt empty password
            if usagePassword == None: self.errorAndExit('Cannot find db.usage.password in %s'%os.path.join(self.dbConfPath, 'db.properties'))
            self.putDbProperty('db.usage.password', formatEncryptResult(encrypt(usagePassword)))

        self.info("Processing encryption ...", None)
        self.putDbProperty("db.cloud.encryption.type", self.encryptiontype)
        saveMgmtServerSecretKey()
        encryptDBSecretKey()
        encryptDBPassword()
        self.info(None, True)

    def parseOptions(self):
        def parseOtherOptions():
            if self.options.rootcreds:
                self.rootuser,self.rootpassword = parseUserAndPassword(self.options.rootcreds)
                if self.rootuser == self.user:
                    self.errorAndExit("--deploy-as= user name cannot be the user name supplied for the connection credentials")

                self.info("Mysql root user name:%s"%self.rootuser, True)
                self.info("Mysql root user password:%s"%"******", True)

            if self.options.serversetup:
                if not self.options.rootcreds:
                    self.errorAndExit("--auto= requires valid --deploy-as= credentials")
                if os.path.isfile(self.options.serversetup):
                    self.errorAndExit("%s is not a valid file"%self.options.serversetup)
                self.serversetup = self.options.serversetup
                self.info("User specified server-setup.sql file at %s"%self.serversetup, True)

            if self.options.mshostip:
                self.ip = self.options.mshostip
                self.info("Using specified cluster management server node IP %s" % self.options.mshostip, True)

            self.encryptiontype = self.options.encryptiontype
            if self.encryptiontype == "env":
                self.mgmtsecretkey = os.getenv("CLOUD_SECRET_KEY")
                if not self.mgmtsecretkey:
                    self.errorAndExit("Please set environment variable CLOUD_SECRET_KEY if the encryption type is 'env'")
            else:
                self.mgmtsecretkey = self.options.mgmtsecretkey
            self.dbsecretkey = self.options.dbsecretkey
            self.isDebug = self.options.debug
            if self.options.dbConfPath:
              self.dbConfPath = self.options.dbConfPath
            if self.options.dbFilesPath:
              self.dbFilesPath = self.options.dbFilesPath
            if self.options.encryptionKeyFile:
              self.encryptionKeyFile = self.options.encryptionKeyFile
            if self.options.encryptionJarPath:
              self.encryptionJarPath = self.options.encryptionJarPath
            if self.options.mysqlbinpath:
              self.mysqlBinPath = self.options.mysqlbinpath
            if self.options.skipUsersAutoCreation:
                self.skipUsersAutoCreation = self.options.skipUsersAutoCreation

            if self.options.encryptorVersion:
              self.encryptorVersion = "--encryptorversion %s" % self.options.encryptorVersion
            else:
              self.encryptorVersion = ""

        def parseUserAndPassword(cred):
            stuff = cred.split(':')
            if len(stuff) != 1 and len(stuff) != 2:
                self.errorAndExit("Invalid user name and password format, must be in format of user:password (%s)"%cred)
            user = stuff[0]
            if len(user) < 1:
                self.errorAndExit("Invalid user name and password format, must be in format of user:password, user name can not be empty")
            if len(stuff) == 1:
                password = ''
            else:
                password = stuff[1]

            forbidden = "' \\`"
            for f in forbidden:
                if f in user: self.errorAndExit("User name cannot have the %r characters"%f)
                if f in password: self.errorAndExit("Password cannot have the %r characters"%f)
            return user, password

        def parseCasualCredit():
            def parseHostInfo(info):
                stuff = info.split(":")
                if len(stuff) == 1:
                    host = stuff[0]
                    port = 3306
                elif len(stuff) == 2:
                    host = stuff[0]
                    try: port = int(stuff[1])
                    except ValueError: self.errorAndExit("The database port must be an integer (%s)"%stuff[1])
                    if port < 1: self.errorAndExit("The database port must be a positive integer (%s)"%stuff[1])
                else:
                    self.errorAndExit("Invalid host and port format, it must be in format of host:port (%s)"%info)
                return host, port

            if len(self.args) == 0:
                self.errorAndExit("Please specify user:password@hostname")
            if len(self.args) > 1:
                self.errorAndExit("There are more than one parameters for user:password@hostname (%s)"%self.args)

            arg = self.args[0]
            try:
                try:
                    splitIndex = arg.rindex('@')
                except ValueError:
                    # If it failed to find @, use host=localhost
                    splitIndex = len(arg)
                    arg += "@localhost"
            finally:
                stuff = [arg[:splitIndex], arg[splitIndex+1:]]

            self.user,self.password = parseUserAndPassword(stuff[0])
            self.host,self.port = parseHostInfo(stuff[1])

            self.info("Mysql user name:%s"%self.user, True)
            self.info("Mysql user password:%s"%"******", True)
            self.info("Mysql server ip:%s"%self.host, True)
            self.info("Mysql server port:%s"%self.port, True)

        def validateParameters():
            if self.options.schemaonly and self.rootuser != None:
                self.errorAndExit("--schema-only and --deploy-as cannot be passed together\n")
            if self.encryptiontype != 'file' and self.encryptiontype != 'web' and self.encryptiontype != 'env':
                self.errorAndExit('Wrong encryption type %s, --encrypt-type can only be "file" or "web" or "env"' % self.encryptiontype)

    #---------------------- option parsing and command line checks ------------------------
        usage = """%prog user:[password]@mysqlhost:[port] [--deploy-as=rootuser:[rootpassword]] [--auto=/path/to/server-setup.xml] [-e ENCRYPTIONTYPE] [-m MGMTSECRETKEY] [-k DBSECRETKEY] [-g ENCRYPTORVERSION] [--debug]

    This command sets up the CloudStack Management Server and CloudStack Usage Server database configuration (connection credentials and host information) based on the first argument.

    If the --deploy-as option is present, this command will also connect to the database using the administrative credentials specified as the value for the --deploy-as argument, construct the database environment needed to run the CloudStack Management Server, and alter the password specified for the user in the first argument.  In this case, the user name specified in --deploy-as= cannot be the same as the user name specified for the connection credentials that the CloudStack Management Server will be set up with.

    If a server-setup.xml cloud setup information file is specified with the --auto option, this command will also construct a customized database environment according to the cloud setup information in the file.

    The port and the password are optional and can be left out..  If host is omitted altogether, it will default to localhost.

    The encryptor version is optional. The options are V1 and V2. If it is not set, the default encryptor will be used.

    Examples:

    %prog cloud:secret
        sets user cloud and password 'secret' up in
        @MSCONF@/db.properties, using localhost as the
        database server

    %prog sheng:rules@192.168.1.1
        sets these credentials up in @MSCONF@/db.properties

    %prog alex:founder@1.2.3.4 --deploy-as=root:nonsense
        sets alex up as the MySQL user, then connects as the root user
        with password 'nonsense', and recreates the databases, creating
        the user alex with password 'founder' as necessary

    %prog alex:founder@1.2.3.4 --deploy-as=root:nonsense -e file -m password -k dbpassword -g V2
        In addition actions performing in above example, using 'password' as management server encryption key
        and 'dbpassword' as database encryption key, saving management server encryption key to a file as the
        encryption type specified by -e is file.
        The credentials in @MSCONF@/db.properties are encrypted by encryptor V2 (AeadBase64Encryptor).
        The db.cloud.encryptor.version is also set to V2. Sensitive values in cloudstack databases will be
        encrypted by the encryptor V2 using the database encryption key.

    %prog alena:tests@5.6.7.8 --deploy-as=root:nonsense --auto=/root/server-setup.xml
        sets alena up as the MySQL user, then connects as the root user
        with password 'nonsense' to server 5.6.7.8, then recreates the
        databases and sets up the alena user, then performs an automated
        database setup using the information in server-setup.xml
    """
        self.parser = OptionParser(usage=usage)
        self.parser.add_option("-v", "--debug", action="store_true", dest="debug", default=False,
                          help="If enabled, print the commands it will run as they run")
        self.parser.add_option("-d", "--deploy-as", action="store", type="string", dest="rootcreds", default="",
                          help="Colon-separated user name and password of a MySQL user with administrative privileges")
        self.parser.add_option("-s", "--schema-only", action="store_true", dest="schemaonly", default=False,
                          help="Creates the db schema only without having to pass root credentials - " \
                               "Please note: The databases (cloud, cloud_usage) and user (cloud) has to be configured " \
                               "manually prior to running this script when using this flag.")
        self.parser.add_option("--force-recreate", action="store_true", dest="forcerecreate", default=False,
                               help="Force recreation of the existing DB databases and schemas. This option is disabled by default." \
                               "Please note: The databases (cloud, cloud_usage) and its tables data will be lost and recreated.")

        self.parser.add_option("-a", "--auto", action="store", type="string", dest="serversetup", default="",
                          help="Path to an XML file describing an automated unattended cloud setup")
        self.parser.add_option("-e", "--encrypt-type", action="store", type="string", dest="encryptiontype", default="file",
                          help="Encryption method used for db password encryption. Valid values are file, web and env. Default is file.")
        self.parser.add_option("-m", "--managementserver-secretkey", action="store", type="string", dest="mgmtsecretkey", default="password",
                          help="Secret key used to encrypt confidential parameters in db.properties. A string, default is password")
        self.parser.add_option("-k", "--database-secretkey", action="store", type="string", dest="dbsecretkey", default="password",
                          help="Secret key used to encrypt sensitive database values. A string, default is password")
        self.parser.add_option("-i", "--mshost", action="store", type="string", dest="mshostip", default="",
                          help="Cluster management server host IP. A string, by default it will try to detect a local IP")
        self.parser.add_option("-r", "--regionid", action="store", type="string", dest="regionid", default="1",
                          help="Region Id for the management server cluster")
        self.parser.add_option("-c", "--db-conf-path", action="store", dest="dbConfPath", help="The path to find db.properties which hold db properties")
        self.parser.add_option("-f", "--db-files-path", action="store", dest="dbFilesPath", help="The path to find sql files to create initial database(s)")
        self.parser.add_option("-j", "--encryption-jar-path", action="store", dest="encryptionJarPath", help="The cloudstack jar to be used to encrypt the values in db.properties")
        self.parser.add_option("-n", "--encryption-key-file", action="store", dest="encryptionKeyFile", help="The name of the file in which encryption key to be generated")
        self.parser.add_option("-g", "--encryptor-version", action="store", dest="encryptorVersion", default="V2",
                               help="The encryptor version to be used to encrypt the values in db.properties")
        self.parser.add_option("-b", "--mysql-bin-path", action="store", dest="mysqlbinpath", help="The mysql installed bin path")
        self.parser.add_option("-u", "--skip-users-auto-creation", action="store_true", dest="skipUsersAutoCreation",
                               help="Indicates whether to skip the auto-creation of users in the database. Use this flag when your database users " \
                                    "are already configured and you only want to populate the db.properties file.")
        (self.options, self.args) = self.parser.parse_args()
        parseCasualCredit()
        parseOtherOptions()
        validateParameters()

    def run(self):
        try:
            self.preRun()
            self.parseOptions()
            self.checkSystemSetup()
            self.grabSystemInfo()
            self.prepareDBFiles()
            self.setupDBSchema()
            self.processEncryptionStuff()
            self.finalize()
        finally:
            self.postRun()

        print('')
        print("CloudStack has successfully initialized database, you can check your database configuration in %s"%os.path.join(self.dbConfPath, 'db.properties'))
        print('')

if __name__ == "__main__":
   o = DBDeployer()
   o.run()
